using System;
using System.Collections.Generic;
using System.Linq;

namespace ByteFarm.ImageMetaData.Format.Tiff
{
    public class TiffDirectoryEntryFactory : DirectoryEntryFactory<Tag, FieldType>
    {
        public TiffDirectoryEntryFactory(Reader reader)
            : base(reader)
        {
        }

        protected override DirectoryEntry<Tag, FieldType> CreateDirectoryEntryInternal()
        {
            ushort tag = Reader.ReadUInt16();
            ushort type = Reader.ReadUInt16();
            int count = (int) Reader.ReadUInt32();

            switch ((FieldType) type)
            {
                case FieldType.Ascii:
                    return new DirectoryEntry<Tag, FieldType, String>(tag, type, count,
                                                                      new List<String>(
                                                                          new[]
                                                                              {
                                                                                  new string(
                                                                                      Read(count, o => o.ReadChar()).
                                                                                          ToList().Take(count - 1).
                                                                                          ToArray())
                                                                              }));
                case FieldType.Byte:
                    return new DirectoryEntry<Tag, FieldType, Byte>(tag, type, count,
                                                                    new List<Byte>(Read(count, o => o.ReadByte())));
                case FieldType.Double:
                    return new DirectoryEntry<Tag, FieldType, Double>(tag, type, count,
                                                                      new List<Double>(Read(count, o => o.ReadDouble())));
                case FieldType.Single:
                    return new DirectoryEntry<Tag, FieldType, Single>(tag, type, count,
                                                                      new List<Single>(Read(count, o => o.ReadSingle())));
                case FieldType.Int16:
                    return new DirectoryEntry<Tag, FieldType, Int16>(tag, type, count,
                                                                     new List<Int16>(Read(count, o => o.ReadInt16())));
                case FieldType.Int32:
                    return new DirectoryEntry<Tag, FieldType, Int32>(tag, type, count,
                                                                     new List<Int32>(Read(count, o => o.ReadInt32())));

                case FieldType.Rational:
                    return new DirectoryEntry<Tag, FieldType, Rational>(tag, type, count,
                                                                        new List<Rational>(Read(count,
                                                                                                o => o.ReadRational())));
                case FieldType.SByte:
                    return new DirectoryEntry<Tag, FieldType, SByte>(tag, type, count,
                                                                     new List<SByte>(Read(count, o => o.ReadSByte())));
                case FieldType.UInt16:
                    return new DirectoryEntry<Tag, FieldType, UInt16>(tag, type, count,
                                                                      new List<UInt16>(Read(count, o => o.ReadUInt16())));
                case FieldType.UInt32:
                    return new DirectoryEntry<Tag, FieldType, UInt32>(tag, type, count,
                                                                      new List<UInt32>(Read(count, o => o.ReadUInt32())));

                case FieldType.URational:
                    return new DirectoryEntry<Tag, FieldType, URational>(tag, type, count,
                                                                         new List<URational>(Read(count,
                                                                                                  o => o.ReadURational())));
                default:
                    return null;
            }
        }


        private static int SizeOf<T>()
        {
            Type t = typeof (T);

            if (t == typeof (Int16))
                return sizeof (Int16);

            if (t == typeof (Int32))
                return sizeof (Int32);

            if (t == typeof (UInt16))
                return sizeof (UInt16);

            if (t == typeof (UInt32))
                return sizeof (UInt32);

            if (t == typeof (byte))
                return sizeof (byte);

            if (t == typeof (sbyte))
                return sizeof (sbyte);

            if (t == typeof (char))
                return sizeof (char);

            if (t == typeof (float))
                return sizeof (float);

            if (t == typeof (double))
                return sizeof (double);

            if (t == typeof (Rational))
                return sizeof (Int32)*2;

            if (t == typeof (URational))
                return sizeof (UInt32)*2;

            throw new NotImplementedException();
        }

        private IEnumerable<T> Read<T>(int count, Func<TiffReader, T> selector)
        {
            int dsize = SizeOf<T>();
            if (dsize*count <= 4)
            {
                for (int i = 0; i < count; i++)
                    yield return selector((TiffReader) Reader);

                for (int i = 0; i < 4 - ((dsize*count)%4); i++)
                    Reader.ReadByte();

                yield break;
            }

            uint dataoffset = Reader.ReadUInt32();
            long position = Reader.Position;
            Reader.Seek(dataoffset);

            for (int i = 0; i < count; i++)
                yield return selector((TiffReader) Reader);

            Reader.Seek(position);
        }
    }
}